package org.moonlight.push.server.vivo;

import com.alibaba.fastjson.JSONObject;
import com.vivo.push.sdk.notofication.Message;
import com.vivo.push.sdk.notofication.Result;
import com.vivo.push.sdk.notofication.TargetMessage;
import com.vivo.push.sdk.server.Sender;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.moonlight.push.server.BaseMessage;

import java.util.*;

/**
 * vivo 手机消息推送 - client
 *
 * 接入文档: https://dev.vivo.com.cn/documentCenter/doc/363#w2-06771799
 *          参数、返回值的详细描述去这里找: https://dev.vivo.com.cn/documentCenter/doc/362#w2-24351194
 * 推送平台: https://dev.vivo.com.cn/promote/pushNews
 *
 * @author Moonlight
 * @date 2022-10-22 11:49
 */
@Slf4j
public class VivoClient {
    private final static String LOCK_VIVO_AUTH_TOKEN = "lock_vivo_auth_token";
    private final static String VIVO_AUTH_TOKEN_CACHE = "vivo_auth_token_cache";
    private final static Integer APP_ID = null;
    private final static String APP_KEY = "";
    private final static String APP_SECRET = "";

    /**
     * 标记位 - 标识是否 IMOppoClient 是否初始化 - false 表示尚未初始化
     **/
    private boolean init = false;
    /**
     * Vivo Push authToken - 要进行PUSH推送，任何接入方都要有个鉴权操作。
     * 获得authToken用于各类推送。一个appId可对应多个token，24小时过期，业务方做中心缓存，1-2小时更新一次。
     * 限制：频率限制，单个app 10次/s。超频返回错误码10094。
     * 可以考虑的一个优化是: 同时缓存多个token记在内存里, 每个token自己记录自己的刷新时间, 到时间就去自己更新自己. 或者直接扔到redis里设置过期时间
     **/
    private String authToken;
    /**
     * Vivo Push authToken 的刷新时间
     **/
    private Long authTokenRefreshTime;
    /**
     * Sender 对象 - Vivo SDK提供Sender类封装了Vivo Push推送服务的各类服务接口。因此，使用SDK推送消息或调用其他服务，首先要创建Sender对象。
     * change by moonlight 不能复用sender对象, 每次请求前都需要重新构建一个sender对象, 没太搞明白为啥不能服用, 应该和HtppClient的某些实现有关
     **/
//    private Sender sender;

    private final static VivoClient INSTANCE = new VivoClient();

    private VivoClient() {
        if (!init()) {
            throw new RuntimeException("VivoClient初始化失败");
        }
    }

    public static VivoClient getInstance() {
        return INSTANCE;
    }

    private boolean init() {
        if (init) {
            return true;
        }
        if (APP_ID == null) {
            log.error("[VivoClient init] VivoClient初始化失败. vivo app id 不应该为空");
            return false;
        }
        if (StringUtils.isBlank(APP_KEY)) {
            log.error("[VivoClient init] VivoClient初始化失败. vivo app key 不应该为空");
            return false;
        }
        if (StringUtils.isBlank(APP_SECRET)) {
            log.error("[VivoClient init] VivoClient初始化失败. vivo app secret 不应该为空");
            return false;
        }
        try {
            if (refreshAuthToken() == null) {
                log.error("[VivoClient init] VivoClient初始化失败. 根据appId[{}]appKey[{}]appSecret[{}]获取 AuthToken 失败. ", APP_ID, APP_KEY, APP_SECRET);
                return false;
            }
        } catch (Exception e) {
            log.error("[VivoClient init] VivoClient初始化失败. 根据appId[{}]appKey[{}]appSecret[{}]获取 AuthToken 时出现异常. ", APP_ID, APP_KEY, APP_SECRET, e);
            return false;
        }

        this.init = true;
        return true;
    }

    /**
     * 功能描述:
     * 要进行PUSH推送，任何接入方都要有个鉴权操作。获得authToken用于各类推送。
     * 接口返回的参数authToken，一个appId可对应多个token，24小时过期，业务方做中心缓存，1-2小时更新一次。
     * 限制：频率限制，单个app 10次/s。超频返回错误码10094。
     * https://dev.vivo.com.cn/documentCenter/doc/363#w2-06771799
     * 主要逻辑:
     * 注意事项:
     *
     * @author moonlight
     * @date 2022/10/22 14:58
     **/
    private Sender refreshAuthToken() throws Exception {
        Sender sender;
        if (StringUtils.isNotBlank(this.authToken) && this.authTokenRefreshTime != null && this.authTokenRefreshTime > System.currentTimeMillis()) {
            sender = new Sender(APP_SECRET);
            sender.setAuthToken(this.authToken);
            return sender;
        }
        try {
            // 发送鉴权请求
            Result result = new Sender(APP_SECRET).getToken(APP_ID, APP_KEY);
            log.info("[VivoClient refreshAuthToken] 根据 appSecret[{}] appId[{}] appKey[{}] 获取 AuthToken. result[{}]", APP_SECRET, APP_ID, APP_KEY, result);

            if (result == null || StringUtils.isBlank(result.getAuthToken())) {
                this.authToken = null;
                this.authTokenRefreshTime = null;
                return null;
            }
            // 如鉴权请求发送成功，获得authToken
            this.authToken = result.getAuthToken();
            // 过期时间 - Vivo的文档说 1-2小时更新一次, 我们按照 1 小时更新一次
            this.authTokenRefreshTime = System.currentTimeMillis() + (3600 * 1000);
            sender = new Sender(APP_SECRET);
            sender.setAuthToken(this.authToken);
            log.info("[VivoClient refreshAuthToken] 获取token成功. token[{}] authTokenRefreshTime[{}]", this.authToken, this.authTokenRefreshTime);
        } catch (Exception e) {
            log.error("[VivoClient refreshAuthToken] 根据 appSecret[{}] appId[{}] appKey[{}] 获取 AuthToken 出现异常", APP_SECRET, APP_ID, APP_KEY, e);
            this.authToken = null;
            this.authTokenRefreshTime = null;
            return null;
        }
        return sender;
    }

    /**
     * 功能描述:
     * 根据 BaseMessage 创建一个 Vivo Message.Build 对象
     * 这个对象只会设置 通知类型、通知标题、通知内容、点击跳转类型、用户请求唯一标识、消息类型等基础信息
     * 并不会指明 regId、alias 之类的信息，这些应该是在具体的推送方法中去设置
     * 主要逻辑:
     * 注意事项:
     *
     * @param msg 基础消息实体
     * @return Message {@link Message} - https://dev.vivo.com.cn/documentCenter/doc/363#w2-06798783 PUSH推送/推送技术文档/服务端文档/服务端SDK文档/二、发送消息/6.消息体创建
     * @author moonlight
     * @date 2022/10/22 16:44
     **/
    private Message.Builder buildVivoMessageBuild(BaseMessage msg) {
        // 跳转类型
        int skipType = 1;
        return new Message.Builder()
                // 通知类型 1:无，2:响铃，3:振动，4:响铃和振动 必填
                .notifyType(1)
                // 通知标题（用于通知栏消息） 最大20个汉字（一个汉字等于两个英文字符，即最大不超过40个英文字符）必填
                .title(msg.getAlertTitle())
                // 通知内容（用于通知栏消息） 最大50个汉字（一个汉字等于两个英文字符，即最大不超过100个英文字符）必填
                .content(msg.getAlertContent())
                // 点击跳转类型 1：打开APP首页 2：打开链接 3：自定义 4:打开app内指定页面 必填
                .skipType(4)
                // 跳转内容 跳转类型为2时，跳转内容最大1000个字符，跳转类型为3或4时，跳转内容最大1024个字符，skipType传3需要在onNotificationMessageClicked回调函数中自己写处理逻辑 非必填
                // vivo的傻逼文档并没有说明当skipType 传 3 时，如果没有实现onNotificationMessageClicked回调函数，会导致消息并发送！！！
                // 另外 vivo 不接受 #Intent;launchFlags=0x4000000;component=域名/安卓Activity全类名;S.caseId=4127;i.intoType=1;end 这种格式
                // 必须使用  intent://域名#Intent;launchFlags=0x4000000;component=域名/安卓Activity全类名;S.caseId=4127;i.intoType=1;end
                .skipContent("")
                // 用户请求唯一标识 最大64字符 必填
                .requestId(msg.getMsgId())
                // 消息类型 0：运营类消息，1：系统类消息。不填默认为0 非必填
                .classification(1)
                // 消息保留时长 单位：秒，取值至少60秒，最长7天。当值为空时，默认一天 非必填 - 不填会报错，真的傻逼.文档说非必填，sdk又不能不填
                .timeToLive(24 * 60 * 60);
                // 推送模式 0：正式推送；1：测试推送，不填默认为0（测试推送，只能给web界面录入的测试用户推送；审核中应用，只能用测试推送）非必填
//                .pushMode(0)
                // 网络方式 -1：不限，1：wifi下发送，不填默认为-1 非必填
//                .networkType(-1);
        // 应用订阅PUSH服务器得到的id 仅单推时需设定（其它推送无需设定该项）  长度23个字符（regId，alias 两者需一个不为空，当两个不为空时，取regId）非必填
//                .regId(msg.getAlias())
        // 别名 仅单推时需设定（其它推送无需设定该项） 长度不超过40字符（regId，alias两者需一个不为空，当两个不为空时，取regId）非必填
//                .alias(msg.getAlias())
        // 推送模式 0：正式推送；1：测试推送，不填默认为0（测试推送，只能给web界面录入的测试用户推送；审核中应用，只能用测试推送）非必填
//                .pushMode()
        // 高级特性（详见目录：一.公共——5.高级特性 extra）非必填
//                .extra()
        // 客户端自定义键值对 自定义key和Value键值对个数不能超过10个，且长度不能超过1024字符, key和Value键值对总长度不能超过1024字符  非必填
//                .clientCustomMap()
        // 网络方式 -1：不限，1：wifi下发送，不填默认为-1 非必填
//                .networkType()
        // 跳转内容 跳转类型为2时，跳转内容最大1000个字符，跳转类型为3或4时，跳转内容最大1024个字符，skipType传3需要在onNotificationMessageClicked回调函数中自己写处理逻辑 非必填
//                .skipContent()
        // 消息保留时长 单位：秒，取值至少60秒，最长7天。当值为空时，默认一天 非必填
//                .timeToLive()
    }

    /**
     * 功能描述:
     * 接入方携带消息内容以及用户regId（或alias：别名）进行通知消息推送。针对每个用户发送不同的通知。
     * https://dev.vivo.com.cn/documentCenter/doc/363#w2-06771799
     * 主要逻辑:
     * 注意事项:
     *
     * @param msg 基础消息实体
     * @return Result vivo 推送服务调用结果 {@link Result}
     * @author moonlight
     * @date 2022/10/22 15:01
     **/
    public Result sendToSingleUser(BaseMessage msg) throws Exception {
        Sender sender;
        if ((sender = refreshAuthToken()) == null) {
            log.error("[VivoClient sendToSingleUser] 获取authToken失败 无法推送消息[{}]", msg);
            throw new RuntimeException("获取authToken失败 无法推送消息");
        }

        Message message = buildVivoMessageBuild(msg)
//                .alias(msg.getAlias())
                .regId(msg.getReceiverId())
                .build();

        log.info("[VivoClient sendToSingleUser] 准备推送消息 msg[{}] vivoMessage[{}]", msg, JSONObject.toJSON(message));

        Result result = sender.sendSingle(message);

        log.info("[VivoClient sendToSingleUser] 向[{}]推送消息[{}]完成. 原始消息[{}] 推送结果[{}]", msg.getReceiverId(), JSONObject.toJSON(message), msg, result);

        return result;
    }

    /**
     * 功能描述:
     * 保存群推消息. 同一条信息覆盖多个用户。此方法需与 批量推送用户 方法配套使用，批量推送用此方法返回的taskId批量发送用户。
     * 主要逻辑:
     * 注意事项:
     *
     * @param msg 基础消息实体
     * @return String taskId {@link Result#getTaskId()}
     * @author moonlight
     * @date 2022/10/22 16:54
     **/
    private Result saveBatchPushMsg(BaseMessage msg) throws Exception {
        Sender sender;
        if ((sender = refreshAuthToken()) == null) {
            log.error("[VivoClient sendToSingleUser] 获取authToken失败 无法推送消息[{}]", msg);
            throw new RuntimeException("获取authToken失败 无法推送消息");
        }

        Message message = buildVivoMessageBuild(msg).build();

        Result result = sender.saveListPayLoad(message);

        log.info("[VivoClient saveBatchPushMsg] 保存群推消息[{}]完成. 原始消息[{}] 调用结果[{}]", JSONObject.toJSON(message), msg, result);

        return result;
    }

    /**
     * 功能描述:
     * 批量发送给用户。该方法与 保存群推消息 方法配套使用，首先通过 保存群推消息 获得taskId,然后使用该taskId，根据regIds或者aliases进行批量推送。
     * 主要逻辑:
     * 注意事项:
     *
     * @param msg       基础消息实体
     * @param targetIds 用户信息
     * @return Result vivo 推送服务调用结果 {@link Result}
     * @author moonlight
     * @date 2022/10/22 16:38
     **/
    public Result sendToMultiUser(BaseMessage msg, List<String> targetIds) throws Exception {
        if (targetIds == null || targetIds.size() == 0) {
            log.error("[VivoClient sendToMultiUser] targetIds不能为空");
            throw new RuntimeException("参数错误, targetIds不能为空");
        }
        if (targetIds.size() > 1000) {
            log.error("[VivoClient sendToMultiUser] targetIds不能超过1000. vivo sdk 要求 batch最大长度为1000，超出会导致请求失败");
            throw new RuntimeException("参数错误, targetIds不能超过1000");
        }

        log.info("[VivoClient sendToMultiUser] 准备向[{}]位用户推送msg[{}]. 第一位用户[{}]", targetIds.size(), msg, targetIds.get(0));

        Result task = saveBatchPushMsg(msg);
        if (StringUtils.isBlank(task.getTaskId())) {
            log.error("[VivoClient sendToMultiUser] 批量发送给用户失败, 获取到的taskResult[{}]TaskId[{}]为空 message[{}]", task, task.getTaskId(), msg);
            return task;
        }

        Sender sender;
        if ((sender = refreshAuthToken()) == null) {
            log.error("[VivoClient sendToMultiUser] 获取authToken失败 无法推送消息[{}]", msg);
            throw new RuntimeException("获取authToken失败 无法推送消息");
        }
        // 构建批量推送用户群
        Set<String> regSet = new HashSet<>(targetIds);

        TargetMessage message = new TargetMessage.Builder()
                // regId列表，个数大于等于2，小于等于1000，regId长度23个字符（regIds，aliases 两者需一个不为空，两个都不为空时，取regIds）非必填
                .regIds(regSet)
                // 别名列表，个数大于等于2，小于等于1000，长度不超过40字符（regIds，aliases 两者需一个不为空，两个都不为空时，取regIds） 非必填
//                .aliases(regSet)
                // 公共消息任务号 调用saveBatchPushMsg返回的taskId  必填项
                .taskId(task.getTaskId())
                // 用户请求唯一标识   最大64字符 必填项
                .requestId(msg.getMsgId()).build();

        Result result = sender.sendToList(message);

        log.info("[VivoClient sendToMultiUser] 推送消息[{}]完成. 原始消息[{}] result[{}] desc[{}]", JSONObject.toJSON(message), msg, result.getResult(), result.getDesc());

        return result;
    }

    public static void demo() throws Exception {
        //注册登录开发平台网站获取到的appSecret
        Sender sender = new Sender(APP_SECRET);
        //注册登录开发平台网站获取到的appId和appKey
        Result result = sender.getToken(APP_ID, APP_KEY);
        System.out.println("result: " + result);
        sender.setAuthToken(result. getAuthToken());
        Message singleMessage = new Message.Builder()
                //该测试手机设备订阅推送所得的regid，且已添加为测试设备
                .regId("10239_358549323d8e70803feb74126b67db35")
                .notifyType(3)
                .title("try_title")
                .content("try-content")
                .timeToLive(1000)
                .skipType(2)
                .skipContent("http://www.vivo.com")
                .networkType(-1)
                .requestId("1234567890123456")
                .pushMode(1)
                .build();
        Result resultMessage = sender.sendSingle(singleMessage);
        System.out.println(resultMessage);
    }

    public static void main(String[] args) throws Exception {
//        demo();

        BaseMessage msg = new BaseMessage();
        msg.setMsgId(UUID.randomUUID().toString());
        msg.setAlertTitle("来自Moonlight的推送");
        msg.setAlertContent("PushServerProject推送VIVO消息");
        msg.setReceiverId("v1-CQKhm36V/PA17iO//EeUq3f4GKmR20CTeytBswK3AM/M40KLndgxfheU");

        VivoClient instance = VivoClient.getInstance();

        Result result = instance.sendToSingleUser(msg);
        System.out.println("result=" + result);

//        Result result = instance.sendToMultiUser(msg, List.of("10239_358549323d8e70803feb74126b67db35"));
//        System.out.println("result=" + result);
    }

}
